package controllers

import (
	"errors"
	"github.com/gorilla/websocket"
	"sync"
)



//该结构体隐藏websocket底层链接
type Connection struct {
	//封装的websocket长链接
	wsConn *websocket.Conn
	//存放读到的消息
	inChan chan []byte
	//存放需要发送的消息
	outChan chan[]byte
	//单字节通道
	closeChan chan byte
	//closeChan需要保证线程安全，所以判断的时候需要加锁
	mutex sync.Mutex
	//状态位标记
	isClosed bool
}

//封装websocket长链接
func InitConnection(wsConn *websocket.Conn)(conn *Connection, err error){

	conn = &Connection{
		wsConn:wsConn,
		inChan:make(chan[]byte,1000),
		outChan:make(chan[]byte,1000),
		closeChan:make(chan byte,1),
	}

	//初始化的时候启动循环读协程,不断从websocket上读消息
	go conn.readLoop()

	//启动写协程，从outchan读消息，并写到websocket
	go conn.writeLoop()

	return
}

//读取消息API，线程安全
func (conn *Connection)ReadMessage()(data []byte , err error){
	select{
	//读取消息
	case data = <- conn.inChan:
	case <- conn.closeChan:
		err = errors.New("connection is closeed")
	}
	return
}

//发送消息API
func (conn *Connection)WriteMessage(data []byte)(err error){

	select{
	case conn.outChan <- data:
	case <- conn.closeChan:
		err = errors.New("connection is closeed")
	}
	return
}


//关闭链接API
func (conn *Connection)Close(){
	// 线程安全，可重入的close，即不同的API可并发调用
	conn.wsConn.Close()
	// 利用标记，让closeChan只关闭一次
	//加锁，锁住这个链接
	conn.mutex.Lock()
	//如果closeChan没有被关闭过,就关闭
	if !conn.isClosed {
		//关闭closeChan，进入select的第二个代码分支，就会跳出整个函数，这样可以同时关闭读写链接
		close(conn.closeChan)
		conn.isClosed = true
	}
	//解锁,释放掉这把锁
	conn.mutex.Unlock()
}


// 内部实现
//不停地从websocket长链接上读消息
func (conn *Connection)readLoop(){
	var(
		data []byte
		err error
	)
	for{
		if _, data , err = conn.wsConn.ReadMessage(); err != nil{
			goto ERR
		}

		//如果conn.inChan满了，就会阻塞在这里，等待inChan有空闲位置
		select{
		case conn.inChan <- data:  //读到之后放到队列里
			//当closeChan关闭的时候，就会进入这个分支
		case <- conn.closeChan:		// closeChan 感知 conn断开
			goto ERR
		}

	}
ERR:
	conn.Close()
}

//不停的发送消息
func (conn *Connection)writeLoop(){
	var(
		data []byte
		err error
	)

	for{
		select{
		case data= <- conn.outChan:
		case <- conn.closeChan:
			goto ERR
		}
		if err = conn.wsConn.WriteMessage(websocket.TextMessage , data); err != nil{
			goto ERR
		}
	}
ERR:
	conn.Close()
}